/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2000-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: ListingErrorHandler.java,v 1.2.4.1 2005/09/15 08:15:46 suresh_emailid Exp $
 */

package com.sun.org.apache.xml.internal.utils;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;

import com.sun.org.apache.xml.internal.res.XMLErrorResources;
import com.sun.org.apache.xml.internal.res.XMLMessages;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 * Sample implementation of similar SAX ErrorHandler and JAXP ErrorListener.
 *
 * <p>This implementation is suitable for various use cases, and
 * provides some basic configuration API's as well to control
 * when we re-throw errors, etc.</p>
 *
 * @author shane_curcuru@us.ibm.com
 * @xsl.usage general
 */
public class ListingErrorHandler implements ErrorHandler, ErrorListener {

  protected PrintWriter m_pw = null;


  /**
   * Constructor ListingErrorHandler; user-supplied PrintWriter.
   */
  public ListingErrorHandler(PrintWriter pw) {
    if (null == pw) {
      throw new NullPointerException(XMLMessages
          .createXMLMessage(XMLErrorResources.ER_ERRORHANDLER_CREATED_WITH_NULL_PRINTWRITER, null));
    }
    // "ListingErrorHandler created with null PrintWriter!");

    m_pw = pw;
  }

  /**
   * Constructor ListingErrorHandler; uses System.err.
   */
  public ListingErrorHandler() {
    m_pw = new PrintWriter(System.err, true);
  }


    /* ======== Implement org.xml.sax.ErrorHandler ======== */

  /**
   * Receive notification of a warning.
   *
   * <p>SAX parsers will use this method to report conditions that
   * are not errors or fatal errors as defined by the XML 1.0
   * recommendation.  The default behaviour is to take no action.</p>
   *
   * <p>The SAX parser must continue to provide normal parsing events
   * after invoking this method: it should still be possible for the
   * application to process the document through to the end.</p>
   *
   * <p>Filters may use this method to report other, non-XML warnings
   * as well.</p>
   *
   * @param exception The warning information encapsulated in a SAX parse exception.
   * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception; only
   * if setThrowOnWarning is true.
   * @see org.xml.sax.SAXParseException
   */
  public void warning(SAXParseException exception)
      throws SAXException {
    logExceptionLocation(m_pw, exception);
    // Note: should we really call .toString() below, since
    //  sometimes the message is not properly set?
    m_pw.println("warning: " + exception.getMessage());
    m_pw.flush();

    if (getThrowOnWarning()) {
      throw exception;
    }
  }


  /**
   * Receive notification of a recoverable error.
   *
   * <p>This corresponds to the definition of "error" in section 1.2
   * of the W3C XML 1.0 Recommendation.  For example, a validating
   * parser would use this callback to report the violation of a
   * validity constraint.  The default behaviour is to take no
   * action.</p>
   *
   * <p>The SAX parser must continue to provide normal parsing events
   * after invoking this method: it should still be possible for the
   * application to process the document through to the end.  If the
   * application cannot do so, then the parser should report a fatal
   * error even if the XML 1.0 recommendation does not require it to
   * do so.</p>
   *
   * <p>Filters may use this method to report other, non-XML errors
   * as well.</p>
   *
   * @param exception The error information encapsulated in a SAX parse exception.
   * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception; only
   * if setThrowOnErroris true.
   * @see org.xml.sax.SAXParseException
   */
  public void error(SAXParseException exception)
      throws SAXException {
    logExceptionLocation(m_pw, exception);
    m_pw.println("error: " + exception.getMessage());
    m_pw.flush();

    if (getThrowOnError()) {
      throw exception;
    }
  }


  /**
   * Receive notification of a non-recoverable error.
   *
   * <p>This corresponds to the definition of "fatal error" in
   * section 1.2 of the W3C XML 1.0 Recommendation.  For example, a
   * parser would use this callback to report the violation of a
   * well-formedness constraint.</p>
   *
   * <p>The application must assume that the document is unusable
   * after the parser has invoked this method, and should continue
   * (if at all) only for the sake of collecting addition error
   * messages: in fact, SAX parsers are free to stop reporting any
   * other events once this method has been invoked.</p>
   *
   * @param exception The error information encapsulated in a SAX parse exception.
   * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception; only
   * if setThrowOnFatalError is true.
   * @see org.xml.sax.SAXParseException
   */
  public void fatalError(SAXParseException exception)
      throws SAXException {
    logExceptionLocation(m_pw, exception);
    m_pw.println("fatalError: " + exception.getMessage());
    m_pw.flush();

    if (getThrowOnFatalError()) {
      throw exception;
    }
  }


    /* ======== Implement javax.xml.transform.ErrorListener ======== */

  /**
   * Receive notification of a warning.
   *
   * <p>{@link javax.xml.transform.Transformer} can use this method to report
   * conditions that are not errors or fatal errors.  The default behaviour
   * is to take no action.</p>
   *
   * <p>After invoking this method, the Transformer must continue with
   * the transformation. It should still be possible for the
   * application to process the document through to the end.</p>
   *
   * @param exception The warning information encapsulated in a transformer exception.
   * @throws javax.xml.transform.TransformerException only if setThrowOnWarning is true.
   * @see javax.xml.transform.TransformerException
   */
  public void warning(TransformerException exception)
      throws TransformerException {
    logExceptionLocation(m_pw, exception);
    m_pw.println("warning: " + exception.getMessage());
    m_pw.flush();

    if (getThrowOnWarning()) {
      throw exception;
    }
  }

  /**
   * Receive notification of a recoverable error.
   *
   * <p>The transformer must continue to try and provide normal transformation
   * after invoking this method.  It should still be possible for the
   * application to process the document through to the end if no other errors
   * are encountered.</p>
   *
   * @param exception The error information encapsulated in a transformer exception.
   * @throws javax.xml.transform.TransformerException only if setThrowOnError is true.
   * @see javax.xml.transform.TransformerException
   */
  public void error(TransformerException exception)
      throws TransformerException {
    logExceptionLocation(m_pw, exception);
    m_pw.println("error: " + exception.getMessage());
    m_pw.flush();

    if (getThrowOnError()) {
      throw exception;
    }
  }

  /**
   * Receive notification of a non-recoverable error.
   *
   * <p>The transformer must continue to try and provide normal transformation
   * after invoking this method.  It should still be possible for the
   * application to process the document through to the end if no other errors
   * are encountered, but there is no guarantee that the output will be
   * useable.</p>
   *
   * @param exception The error information encapsulated in a transformer exception.
   * @throws javax.xml.transform.TransformerException only if setThrowOnError is true.
   * @see javax.xml.transform.TransformerException
   */
  public void fatalError(TransformerException exception)
      throws TransformerException {
    logExceptionLocation(m_pw, exception);
    m_pw.println("error: " + exception.getMessage());
    m_pw.flush();

    if (getThrowOnError()) {
      throw exception;
    }
  }



    /* ======== Implement worker methods ======== */


  /**
   * Print out location information about the exception.
   *
   * Cribbed from DefaultErrorHandler.printLocation()
   *
   * @param pw PrintWriter to send output to
   * @param exception TransformerException or SAXParseException to log information about
   */
  public static void logExceptionLocation(PrintWriter pw, Throwable exception) {
    if (null == pw) {
      pw = new PrintWriter(System.err, true);
    }

    SourceLocator locator = null;
    Throwable cause = exception;

    // Try to find the locator closest to the cause.
    do {
      // Find the current locator, if one present
      if (cause instanceof SAXParseException) {
        // A SAXSourceLocator is a Xalan helper class
        //  that implements both a SourceLocator and a SAX Locator
        //@todo check that the new locator actually has
        //  as much or more information as the
        //  current one already does
        locator = new SAXSourceLocator((SAXParseException) cause);
      } else if (cause instanceof TransformerException) {
        SourceLocator causeLocator = ((TransformerException) cause).getLocator();
        if (null != causeLocator) {
          locator = causeLocator;
        }
      }

      // Then walk back down the chain of exceptions
      if (cause instanceof TransformerException) {
        cause = ((TransformerException) cause).getCause();
      } else if (cause instanceof WrappedRuntimeException) {
        cause = ((WrappedRuntimeException) cause).getException();
      } else if (cause instanceof SAXException) {
        cause = ((SAXException) cause).getException();
      } else {
        cause = null;
      }
    }
    while (null != cause);

    // Formatting note: mimic javac-like errors:
    //  path\filename:123: message-here
    //  systemId:L=1;C=2: message-here
    if (null != locator) {
      String id = (locator.getPublicId() != locator.getPublicId())
          ? locator.getPublicId()
          : (null != locator.getSystemId())
              ? locator.getSystemId() : "SystemId-Unknown";

      pw.print(id + ":Line=" + locator.getLineNumber()
          + ";Column=" + locator.getColumnNumber() + ": ");
      pw.println("exception:" + exception.getMessage());
      pw.println("root-cause:"
          + ((null != cause) ? cause.getMessage() : "null"));
      logSourceLine(pw, locator);
    } else {
      pw.print("SystemId-Unknown:locator-unavailable: ");
      pw.println("exception:" + exception.getMessage());
      pw.println("root-cause:"
          + ((null != cause) ? cause.getMessage() : "null"));
    }
  }


  /**
   * Print out the specific source line that caused the exception,
   * if possible to load it.
   *
   * @param pw PrintWriter to send output to
   * @param locator Xalan wrapper for either a JAXP or a SAX source location object
   */
  public static void logSourceLine(PrintWriter pw, SourceLocator locator) {
    if (null == locator) {
      return;
    }

    if (null == pw) {
      pw = new PrintWriter(System.err, true);
    }

    String url = locator.getSystemId();
    // Bail immediately if we get SystemId-Unknown
    //@todo future improvement: attempt to get resource
    //  from a publicId if possible
    if (null == url) {
      pw.println("line: (No systemId; cannot read file)");
      pw.println();
      return;
    }

    //@todo attempt to get DOM backpointer or other ids

    try {
      int line = locator.getLineNumber();
      int column = locator.getColumnNumber();
      pw.println("line: " + getSourceLine(url, line));
      StringBuffer buf = new StringBuffer("line: ");
      for (int i = 1; i < column; i++) {
        buf.append(' ');
      }
      buf.append('^');
      pw.println(buf.toString());
    } catch (Exception e) {
      pw.println("line: logSourceLine unavailable due to: " + e.getMessage());
      pw.println();
    }
  }


  /**
   * Return the specific source line that caused the exception,
   * if possible to load it; allow exceptions to be thrown.
   *
   * @author shane_curcuru@us.ibm.com
   */
  protected static String getSourceLine(String sourceUrl, int lineNum)
      throws Exception {
    URL url = null;
    // Get a URL from the sourceUrl
    try {
      // Try to get a URL from it as-is
      url = new URL(sourceUrl);
    } catch (java.net.MalformedURLException mue) {
      int indexOfColon = sourceUrl.indexOf(':');
      int indexOfSlash = sourceUrl.indexOf('/');

      if ((indexOfColon != -1)
          && (indexOfSlash != -1)
          && (indexOfColon < indexOfSlash)) {
        // The url is already absolute, but we could not get
        //  the system to form it, so bail
        throw mue;
      } else {
        // The url is relative, so attempt to get absolute
        url = new URL(SystemIDResolver.getAbsoluteURI(sourceUrl));
        // If this fails, allow the exception to propagate
      }
    }

    String line = null;
    InputStream is = null;
    BufferedReader br = null;
    try {
      // Open the URL and read to our specified line
      URLConnection uc = url.openConnection();
      is = uc.getInputStream();
      br = new BufferedReader(new InputStreamReader(is));

      // Not the most efficient way, but it works
      // (Feel free to patch to seek to the appropriate line)
      for (int i = 1; i <= lineNum; i++) {
        line = br.readLine();
      }

    }
    // Allow exceptions to propagate from here, but ensure
    //  streams are closed!
    finally {
      br.close();
      is.close();
    }

    // Return whatever we found
    return line;
  }


    /* ======== Implement settable properties ======== */

  /**
   * User-settable behavior: when to re-throw exceptions.
   *
   * <p>This allows per-instance configuration of
   * ListingErrorHandlers.  You can ask us to either throw
   * an exception when we're called for various warning /
   * error / fatalErrors, or simply log them and continue.</p>
   *
   * @param b if we should throw an exception on warnings
   */
  public void setThrowOnWarning(boolean b) {
    throwOnWarning = b;
  }

  /**
   * User-settable behavior: when to re-throw exceptions.
   *
   * @return if we throw an exception on warnings
   */
  public boolean getThrowOnWarning() {
    return throwOnWarning;
  }

  /**
   * If we should throw exception on warnings; default:false.
   */
  protected boolean throwOnWarning = false;


  /**
   * User-settable behavior: when to re-throw exceptions.
   *
   * <p>This allows per-instance configuration of
   * ListingErrorHandlers.  You can ask us to either throw
   * an exception when we're called for various warning /
   * error / fatalErrors, or simply log them and continue.</p>
   *
   * <p>Note that the behavior of many parsers/transformers
   * after an error is not necessarily defined!</p>
   *
   * @param b if we should throw an exception on errors
   */
  public void setThrowOnError(boolean b) {
    throwOnError = b;
  }

  /**
   * User-settable behavior: when to re-throw exceptions.
   *
   * @return if we throw an exception on errors
   */
  public boolean getThrowOnError() {
    return throwOnError;
  }

  /**
   * If we should throw exception on errors; default:true.
   */
  protected boolean throwOnError = true;


  /**
   * User-settable behavior: when to re-throw exceptions.
   *
   * <p>This allows per-instance configuration of
   * ListingErrorHandlers.  You can ask us to either throw
   * an exception when we're called for various warning /
   * error / fatalErrors, or simply log them and continue.</p>
   *
   * <p>Note that the behavior of many parsers/transformers
   * after a fatalError is not necessarily defined, most
   * products will probably barf if you continue.</p>
   *
   * @param b if we should throw an exception on fatalErrors
   */
  public void setThrowOnFatalError(boolean b) {
    throwOnFatalError = b;
  }

  /**
   * User-settable behavior: when to re-throw exceptions.
   *
   * @return if we throw an exception on fatalErrors
   */
  public boolean getThrowOnFatalError() {
    return throwOnFatalError;
  }

  /**
   * If we should throw exception on fatalErrors; default:true.
   */
  protected boolean throwOnFatalError = true;

}
